﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using APICommon;
using DataProvider;

namespace APIComparer
{
    public class CompareAssemblyProcessor : MarshalByRefObject
    {
        private void ReportProgress(Action<Progress> progressAction, string message)
        {
            if (progressAction != null)
            {
                progressAction(Progress.GetProgress(message));
            }
        }

        private void ValidatePath(string path)
        {
            if (path == null) throw new ArgumentNullException("path");
            if (!System.IO.File.Exists(path))
                throw new ArgumentException(String.Format("path {0} does not exist", path));
        }

        internal Result Process(string file, Action<Progress> progressAction)
        {
            List<string> errors = new List<string>();

            try
            {
                ValidatePath(file);
                System.Diagnostics.Debug.WriteLine("Started " + file);
                ReportProgress(progressAction, "Processing " + Path.GetFileName(file));

                Assembly assembly = Assembly.LoadFrom(file);
                Type[] types = assembly.GetTypes();
                List<SageField> fields = new List<SageField>();
                List<SageMethod> properties = new List<SageMethod>();
                List<SageMethod> methods = new List<SageMethod>();

                List<Tuple<SageVersion, SageMethod, SageMethod>> changedMethods = new List<Tuple<SageVersion, SageMethod, SageMethod>>();
                List<Tuple<SageVersion, SageMethod>> newMethods = new List<Tuple<SageVersion, SageMethod>>();
                List<Tuple<SageVersion, SageMethod>> removedMethods = new List<Tuple<SageVersion, SageMethod>>();

                Version version = new Version(FileVersionInfo.GetVersionInfo(assembly.Location).ProductVersion);

                SageVersion sageVersion = null;
                using (var db = new APIDBContext())
                {
                    sageVersion = db.Versions.FirstOrDefault(s => s.MajorVersion == version.Major
                        && s.MinorVersion == version.Minor
                        && s.Build == version.Build
                        && s.Revision == version.Revision);
                }
                if (sageVersion == null)
                {
                    sageVersion = new SageVersion(version);
                }

                ReportProgress(progressAction, "Reading types from " + Path.GetFileName(file));
                foreach (Type type in types)
                {
                    if (!type.IsPublic) continue;
                    DataProcessor.Process(file, type, fields, properties, methods, sageVersion);
                }

                // merger properties into methods
                methods.AddRange(properties);

                using (var db = new APIDBContext())
                {
                    SageVersion maxVersion = GetMaxVersionForAssembly(file, db);
                    // Comprare Methods
                    foreach (SageMethod method in methods)
                    {
                        try
                        {
                            ReportProgress(progressAction, string.Format("{0} {1}.{2}", Path.GetFileName(file), method.Type, method.Name));
                            
                            var matchingMethods = db.Methods.Where(s => s.VersionId == maxVersion.Id && s.Name == method.Name
                                && s.Namespace == method.Namespace
                                && s.Type == method.Type).ToList();
                            if (matchingMethods.FirstOrDefault() != null)
                            {
                                var exactMethod = matchingMethods.FirstOrDefault(s => method.MethodSignature == s.MethodSignature);
                                if (exactMethod == null)
                                {
                                    //We have a problem then.
                                    // Try to look for the 'correct' old method which seems to have changed
                                    // first assumption - same number of parameters
                                    SageMethod oldMethod = matchingMethods.FirstOrDefault(s => s.Arguments.Count == method.Arguments.Count);
                                    if (oldMethod == null)
                                    {
                                        // Just pick the first to report. TODO: needs refinement here
                                        oldMethod = matchingMethods.First();
                                    }
                                    changedMethods.Add(new Tuple<SageVersion, SageMethod, SageMethod>(maxVersion, oldMethod.Clone(), method));
                                }
                            }
                            else
                            {
                                //No matching methods found in the old API data base
                                // It must be a new Method
                                newMethods.Add(new Tuple<SageVersion, SageMethod>(maxVersion, method));
                            }
                        }
                        catch (Exception ex)
                        {
                            errors.Add(ex.Message);
                        }
                    }

                    // Process removed methods
                    foreach (Type type in types)
                    {
                        var methodsOfThisTypeInNewVersion = methods.Where(s => s.Namespace == type.Namespace && s.Type == type.Name).ToList();
                        var methodsOfThisTypeInOldVersion = db.Methods.Where(s => s.Namespace == type.Namespace && s.Type == type.Name && s.VersionId == maxVersion.Id).ToList();
                        var commonMethods = methodsOfThisTypeInOldVersion.Intersect(methodsOfThisTypeInNewVersion, new KeyEqualityComparer<SageMethod>(s => s.ToString())).ToList();
                        //removedMethods.AddRange(methodsOfThisTypeInOldVersion.Where(s => commonMethods.FirstOrDefault(f => f.Id == s.Id) == null));
                        HashSet<string> hs = new HashSet<string>(commonMethods.Select(s => s.ToString()));
                        methodsOfThisTypeInOldVersion.RemoveAll(s => hs.Contains(s.ToString()));
                        removedMethods.AddRange(methodsOfThisTypeInOldVersion.Select(s => new Tuple<SageVersion, SageMethod>(maxVersion, s.Clone())));
                    }
                }
                System.Diagnostics.Debug.WriteLine("Completed " + file);
                return new Result() { Errors = errors, Assembly = file, NewMethods = newMethods, ChangedMethods = changedMethods, RemovedMethods = removedMethods };
            }
            catch (Exception ex)
            {
                errors.Add(ex.Message);
                return new Result() { Errors = errors };
            }
        }

        private static SageVersion GetMaxVersionForAssembly(string file, APIDBContext db)
        {
            string assemlyName = Path.GetFileName(file);
            // Get the latest version for this file (assembly):
            var query = db.Methods.Where(s => string.Compare(s.Assembly, assemlyName, true) == 0).Join(db.Versions, c => c.VersionId, cm => cm.Id, (c, cm) => new { Method = c, Version = cm }).Select(s => s);

            int iMaxMajor = query.Select(s => s.Version).Max(s => s.MajorVersion);
            int iMaxMinor = query.Select(s => s.Version).Where(s => s.MajorVersion == iMaxMajor).Max(s => s.MinorVersion);
            int iMaxBuild = query.Select(s => s.Version).Where(s => s.MajorVersion == iMaxMajor && s.MinorVersion == iMaxMinor).Max(s => s.Build);
            int iMaxRevision = query.Select(s => s.Version).Where(s => s.MajorVersion == iMaxMajor && s.MinorVersion == iMaxMinor && s.Build == iMaxBuild).Max(s => s.Revision);
            SageVersion maxVersion = db.Versions.FirstOrDefault(s => s.MajorVersion == iMaxMajor && s.MinorVersion == iMaxMinor && s.Build == iMaxBuild && s.Revision == iMaxRevision);
            return maxVersion;
        }

        public class KeyEqualityComparer<T> : IEqualityComparer<T>
        {
            private readonly Func<T, object> keyExtractor;

            public KeyEqualityComparer(Func<T, object> keyExtractor)
            {
                this.keyExtractor = keyExtractor;
            }

            public bool Equals(T x, T y)
            {
                return this.keyExtractor(x).Equals(this.keyExtractor(y));
            }

            public int GetHashCode(T obj)
            {
                return this.keyExtractor(obj).GetHashCode();
            }
        }


    }
}
